////////////////////////////////////////////////////////////////////////////////////////
//File		:	DXControl.cpp
//Author	:	Nick Bullock - nbullock@fullsail.com
//				Full Sail University
//				DirectX - 03/03/2009
//Purpose	:	Definitions for wrappers to control DirectX9 device interface.
////////////////////////////////////////////////////////////////////////////////////////
#include "DXControl.h"

DXDeviceControl*	DXDeviceControl::m_pInstance = NULL;
unsigned int		DXDeviceControl::m_references = 0;

DXDeviceControl::DXDeviceControl(void){Create();}
DXDeviceControl::DXDeviceControl(const DXDeviceControl &ref){}
DXDeviceControl* DXDeviceControl::operator=(const DXDeviceControl &ref){return this;}

void DXDeviceControl::Create(void)
{
	m_pInstance = NULL;
	m_references = 0;
	m_pd3d = NULL;
	m_pdev = NULL;
	m_pFont = NULL;
	m_pLine = NULL;
	m_pSprite = NULL;
	memset(&m_currentDeviceSettings, 0, sizeof(DXDeviceSettings));
	memset(&m_deviceState, 0, sizeof(DXDeviceState));
	m_screenWidth = 0;
	m_screenHeight = 0;
}

DXDeviceControl::~DXDeviceControl(void)
{
	KillControl();
}

void DXDeviceControl::KillControl(void)
{
	SAFE_RELEASE(m_pFont);
	SAFE_RELEASE(m_pLine);
	SAFE_RELEASE(m_pSprite);
	SAFE_RELEASE(m_pdev);
	SAFE_RELEASE(m_pd3d);
}

DXDeviceControl* DXDeviceControl::GetInstance()
{
	if(!m_pInstance)
	{
		m_pInstance = new DXDeviceControl;
	}

	m_references++;

	return m_pInstance;
}

void DXDeviceControl::ReleaseInstance()
{
	m_references--;

	if(!m_references && m_pInstance)
	{
		delete m_pInstance;
	}
}

LPDIRECT3DDEVICE9 DXDeviceControl::GetDevice()
{
	return m_pdev;
}

LPD3DXFONT DXDeviceControl::GetFont(void)
{
	return m_pFont;
}

LPD3DXLINE DXDeviceControl::GetLine(void)
{
	return m_pLine;
}

LPD3DXSPRITE DXDeviceControl::GetSprite(void)
{
	return m_pSprite;
}

void DXDeviceControl::GetViewMatrix(D3DMATRIX& viewMat)
{
	if(m_deviceState.IsDeviceInitialized && !m_deviceState.LostDevice)
	{
		m_pdev->GetTransform(D3DTS_VIEW, &viewMat);
	}
}

void DXDeviceControl::GetProjectionMatrix(D3DMATRIX& projMat)
{
	if(m_deviceState.IsDeviceInitialized && !m_deviceState.LostDevice)
	{
		m_pdev->GetTransform(D3DTS_PROJECTION, &projMat);
	}
}

BOOL DXDeviceControl::IsDeviceInitialized(void)
{
	return m_deviceState.IsDeviceInitialized;
}

BOOL DXDeviceControl::IsSpriteInitialized(void)
{
	return m_deviceState.IsSpriteInitialized;
}

BOOL DXDeviceControl::IsFontInitialized(void)
{
	return m_deviceState.IsFontInitialized;
}

BOOL DXDeviceControl::IsLineInitialized(void)
{
	return m_deviceState.IsLineInitialized;
}

BOOL DXDeviceControl::IsDeviceWindowed(void)
{
	return m_deviceState.IsWindowed;
}

BOOL DXDeviceControl::IsDeviceWorking(void)
{
	return !m_deviceState.LostDevice;
}

bool DXDeviceControl::InitDevice(HWND hWnd, DXDeviceSettings* display, BOOL windowed, BOOL bVsync)
{
	if(NULL == (m_pd3d = Direct3DCreate9(D3D_SDK_VERSION)))
	{
		// wrong directx installed or none at all
		DXERROR("Error initializing DirectX\nVersion does not match the runtime DLL version.");
	}

	if(display)
	{
		// brought our own settings to use
		m_currentDeviceSettings = *display;
		m_currentDeviceSettings.PresentParams.Windowed = windowed;
	}
	else
	{
		m_currentDeviceSettings.AdapterOrdinal = 0;
		m_currentDeviceSettings.DeviceType = D3DDEVTYPE_HAL;
		m_currentDeviceSettings.BehaviorFlags = D3DCREATE_HARDWARE_VERTEXPROCESSING;

		// use the default wrapper parameters
		m_currentDeviceSettings.PresentParams.Windowed = windowed;
		m_currentDeviceSettings.PresentParams.PresentationInterval	= (bVsync) ? D3DPRESENT_INTERVAL_DEFAULT : D3DPRESENT_INTERVAL_IMMEDIATE;
		//m_currentDeviceSettings.PresentParams.PresentationInterval = D3DPRESENT_INTERVAL_ONE;
		m_currentDeviceSettings.PresentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;

		// add for default 3D
		m_currentDeviceSettings.PresentParams.EnableAutoDepthStencil = true;		// depth stencil
		m_currentDeviceSettings.PresentParams.AutoDepthStencilFormat = D3DFMT_D16;	// stencil foramt
	}

	m_currentDeviceSettings.PresentParams.hDeviceWindow = hWnd;
	if(!m_currentDeviceSettings.PresentParams.Windowed)
	{
		// fullscreen mode needs some extra info for the backbuffers
		if(!FindCurrentDisplaySettings(&m_currentDeviceSettings.PresentParams))
		{
			return false;
		}			
	}

	if(FAILED(m_pd3d->CreateDevice(m_currentDeviceSettings.AdapterOrdinal,
		m_currentDeviceSettings.DeviceType,
		m_currentDeviceSettings.PresentParams.hDeviceWindow,
		m_currentDeviceSettings.BehaviorFlags,
		&m_currentDeviceSettings.PresentParams, &m_pdev)))
	{
		DXERROR("Error initializing device interface.");
	}

	//set the devuce states
	m_deviceState.IsWindowed = m_currentDeviceSettings.PresentParams.Windowed;
	m_deviceState.LostDevice = FALSE;
	m_deviceState.IsDeviceInitialized = TRUE;
	m_pdev->SetRenderState(D3DRS_ZENABLE, true);

	return true;
}

bool DXDeviceControl::InitSprite(void)
{
	if(m_deviceState.IsDeviceInitialized && !m_deviceState.LostDevice)
	{
		if(FAILED(D3DXCreateSprite(m_pdev, &m_pSprite)))
		{
			DXERROR("Error initializing sprite interface.");
		}
		m_deviceState.IsSpriteInitialized = TRUE;
		return true;
	}
	//device not ready or initialized yet
	return false;
}

bool DXDeviceControl::InitFont(char* const fontName)
{
	if(m_deviceState.IsDeviceInitialized && !m_deviceState.LostDevice)
	{
		if(FAILED(D3DXCreateFont(m_pdev, 24, 0, FW_NORMAL, NULL, false, 
			DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, ANTIALIASED_QUALITY,
			DEFAULT_PITCH | FF_DONTCARE, fontName, &m_pFont)))
		{
			DXERROR("Error initializing sprite interface.");
		}
		m_deviceState.IsFontInitialized = TRUE;
		return true;
	}
	//device not ready or initialized yet
	return false;
}

bool DXDeviceControl::InitLine(void)
{
	if(m_deviceState.IsDeviceInitialized && !m_deviceState.LostDevice)
	{
		if(FAILED(D3DXCreateLine(m_pdev, &m_pLine)))
		{
			DXERROR("Error initializing line interface.");
		}
		m_deviceState.IsLineInitialized = TRUE;
		return true;
	}
	//device not ready or initialized yet
	return false;
}

bool DXDeviceControl::ToggleFullScreen(void)
{
	//save a backup copy.
	D3DPRESENT_PARAMETERS oldPresentParams = m_currentDeviceSettings.PresentParams;
	m_currentDeviceSettings.PresentParams.Windowed = !m_currentDeviceSettings.PresentParams.Windowed;
	if(!FindCurrentDisplaySettings(&m_currentDeviceSettings.PresentParams))
	{
		//set the Windowed setting back.
		m_currentDeviceSettings.PresentParams.Windowed = !m_currentDeviceSettings.PresentParams.Windowed;
		return false;
	}

	SetScreenChange(&oldPresentParams);

	//success, toggle the state.
	m_deviceState.IsWindowed = !m_deviceState.IsWindowed;
	return true;
}

bool DXDeviceControl::SetScreenSize(unsigned width, unsigned height)
{
	if(!m_deviceState.IsDeviceInitialized || m_deviceState.LostDevice)
	{
		return false;
	}

	m_screenWidth = width;
	m_screenHeight = height;

	//save a backup copy.
	D3DPRESENT_PARAMETERS oldPresentParams = m_currentDeviceSettings.PresentParams;
	return SetScreenChange(&oldPresentParams);
}

bool DXDeviceControl::SetScreenChange(D3DPRESENT_PARAMETERS* const oldPresentParams)
{
	LONG windowStyleFlags = WS_VISIBLE;
	UINT windowPositionFlags = SWP_SHOWWINDOW | SWP_FRAMECHANGED;
	int nX, nY, nCX, nCY;
	nX = nY = nCX = nCY = 0;

	if(m_currentDeviceSettings.PresentParams.Windowed)
	{
		m_currentDeviceSettings.PresentParams.BackBufferWidth = 0;
		m_currentDeviceSettings.PresentParams.BackBufferHeight = 0;
		/* FullScreen_RefreshRateInHz must be zero for Windowed mode */
		m_currentDeviceSettings.PresentParams.FullScreen_RefreshRateInHz = D3DFMT_UNKNOWN;

		windowStyleFlags |= WS_OVERLAPPEDWINDOW;
		//get coordinates to center the window.
		nX = (GetSystemMetrics(SM_CXSCREEN)>>1) - ((m_screenWidth ? m_screenWidth : DEFAULT_WINDOWED_WIDTH)>>1);
		nY = (GetSystemMetrics(SM_CYSCREEN)>>1) - ((m_screenHeight ? m_screenHeight : DEFAULT_WINDOWED_HEIGHT)>>1);
		nCX = m_screenWidth ? m_screenWidth : DEFAULT_WINDOWED_WIDTH;
		nCY = m_screenHeight ? m_screenHeight : DEFAULT_WINDOWED_HEIGHT;
		SetWindowPos(m_currentDeviceSettings.PresentParams.hDeviceWindow, HWND_TOP, nX, nY, nCX, nCY, windowPositionFlags);
	}
	else
	{	
		if(m_screenWidth)
		{
			// A specified screen width is chosen, don't use the desktop resolution.
			m_currentDeviceSettings.PresentParams.BackBufferWidth = m_screenWidth;
		}
		if(m_screenHeight)
		{
			// A specified screen height is chosen, don't use the desktop resolution.
			m_currentDeviceSettings.PresentParams.BackBufferHeight = m_screenHeight;
		}

		windowStyleFlags |= WS_POPUPWINDOW;
		windowPositionFlags |= SWP_NOMOVE | SWP_NOSIZE;
	}

	SetWindowLong(m_currentDeviceSettings.PresentParams.hDeviceWindow, GWL_STYLE, windowStyleFlags);

	if(!ResetDevice())
	{
		//try again using the default backbuffer values.
		m_currentDeviceSettings.PresentParams.BackBufferWidth = DEFAULT_WINDOWED_WIDTH;
		m_currentDeviceSettings.PresentParams.BackBufferHeight = DEFAULT_WINDOWED_HEIGHT;
		if(!ResetDevice())
		{
			//try reseting back to the old settings.
			m_currentDeviceSettings.PresentParams = *oldPresentParams;
			if(!ResetDevice())
			{
				//bad news.
				DXERROR("Unable to re-establish a device interface.");
			}
		}
	}

	SetWindowPos(m_currentDeviceSettings.PresentParams.hDeviceWindow, HWND_TOP, nX, nY, nCX, nCY, windowPositionFlags);

	return true;
}

bool DXDeviceControl::FindCurrentDisplaySettings(D3DPRESENT_PARAMETERS* const pDisplayParams)
{
	D3DDISPLAYMODE displayMode;
	// Find the current desktop display settings.
	if(FAILED(m_pd3d->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &displayMode)))
	{
		DXERROR("Current display mode of the adapter is out of range.");
	}

	pDisplayParams->BackBufferFormat = displayMode.Format;
	pDisplayParams->BackBufferHeight = displayMode.Height;
	pDisplayParams->BackBufferWidth = displayMode.Width;
	pDisplayParams->FullScreen_RefreshRateInHz = displayMode.RefreshRate;

	return true;
}

bool DXDeviceControl::CheckDevice(void)
{
	HRESULT hr;
	// Test the cooperative level to see if it's okay to render
	if(FAILED(hr = m_pdev->TestCooperativeLevel()))
	{
		// Window is minimized or paused so yield CPU time back to other processes
		Sleep(50);
		m_deviceState.LostDevice = TRUE;
		if(hr == D3DERR_DEVICENOTRESET)
		{
			// The device has been lost but can be reset at this time.
			// true = do something.
			return true;
		}
		// The device has been lost but cannot be reset at this time.  
		// So wait until it can be reset. false = do nothing.
		return false;
	}

	// Nothing wrong with device, set state to not lost, and do nothing else.
	m_deviceState.LostDevice = FALSE;
	return false;
}

bool DXDeviceControl::ResetDevice(void)
{
	//verify the handle
	if(!m_currentDeviceSettings.PresentParams.hDeviceWindow)
	{
		DXERROR("Error assigning a handle to the window.");
	}

	//release all references to video memory resources
	//and delete all stateblocks
	if(m_deviceState.IsFontInitialized)
	{
		if(FAILED(m_pFont->OnLostDevice()))
		{
			DXERROR("Invalid Call on DXFont reset.");
		}
	}
	if(m_deviceState.IsSpriteInitialized)
	{
		if(FAILED(m_pSprite->OnLostDevice()))
		{
			DXERROR("Invalid Call on DXSprite reset.");
		}
	}
	if(m_deviceState.IsLineInitialized)
	{
		if(FAILED(m_pLine->OnLostDevice()))
		{
			DXERROR("Invalid Call on DXLine reset.");
		}
	}

	if(m_deviceState.IsDeviceInitialized)
	{
		//try to reset the device
		if(FAILED(m_pdev->Reset(&m_currentDeviceSettings.PresentParams)))
		{
			m_deviceState.LostDevice = TRUE;
			return false;
		}
	}

	//try to reset any additional interfaces
	if(m_deviceState.IsFontInitialized)
	{
		if(FAILED(m_pFont->OnResetDevice()))
		{
			DXERROR("Invalid Call on DXFont reset.");
		}
	}
	if(m_deviceState.IsSpriteInitialized)
	{
		if(FAILED(m_pSprite->OnResetDevice()))
		{
			DXERROR("Invalid Call on DXSprite reset.");
		}
	}
	if(m_deviceState.IsLineInitialized)
	{
		if(FAILED(m_pLine->OnResetDevice()))
		{
			DXERROR("Invalid Call on DXLine reset.");
		}
	}

	m_deviceState.LostDevice = FALSE;
	return true;
}

void DXDeviceControl::SetViewMatrix(D3DMATRIX& viewMat)
{
	if(m_deviceState.IsDeviceInitialized && !m_deviceState.LostDevice)
	{
		// set view transform
		m_pdev->SetTransform(D3DTS_VIEW, &viewMat);
	}
}

void DXDeviceControl::SetProjectionMatrix(D3DMATRIX& projMat)
{
	if(m_deviceState.IsDeviceInitialized && !m_deviceState.LostDevice)
	{
		// set projection transform
		m_pdev->SetTransform(D3DTS_PROJECTION, &projMat);
	}
}

void DXDeviceControl::BeginRendering(bool stencil, bool target, bool zbuffer,
									 unsigned char red, unsigned char green, unsigned char blue)
{
	if(m_deviceState.IsDeviceInitialized && !m_deviceState.LostDevice)
	{
		DWORD buffers = 0;
		if(stencil)
		{
			buffers |= D3DCLEAR_STENCIL;
		}
		if(target)
		{
			buffers |= D3DCLEAR_TARGET;
		}
		if(zbuffer)
		{
			buffers |= D3DCLEAR_ZBUFFER;
		}
		m_pdev->Clear(0, 0, buffers, D3DCOLOR_XRGB(red,green,blue), 1.0f, 0);
		m_pdev->BeginScene();
	}
}

void DXDeviceControl::EndRendering(void)
{
	if(m_deviceState.IsDeviceInitialized && !m_deviceState.LostDevice)
	{
		m_pdev->EndScene();
		if(m_pdev->Present(NULL, NULL, NULL, NULL) == D3DERR_DEVICELOST)
		{
			m_deviceState.LostDevice = TRUE;
		}
	}
}

bool DXDeviceControl::DrawText(char* text, int xPos, int yPos, 
							   unsigned char red, unsigned char green, unsigned char blue, unsigned char alpha)
{
	if(!m_deviceState.IsFontInitialized)
	{
		// font not initialized.
		return false;
	}
	RECT textRect = {xPos,yPos,
		m_screenWidth ? m_screenWidth : DEFAULT_WINDOWED_WIDTH, 
		m_screenHeight ? m_screenHeight : DEFAULT_WINDOWED_HEIGHT};
	m_pFont->DrawText(0, text, -1, &textRect, 0, D3DCOLOR_RGBA(red, blue, green, alpha));	
	return true;
}